<?php
	class cJsonValidator
	{
		const C_INT_ERROR_INVALID_JSON = -1;
		const C_INT_ERROR_INVALID_SCHEMA = -2;
		const C_INT_ERROR_MATCH_SCHEMA = -3;
		const C_INT_ERROR_MISSING_REQUIRED = -4;
		const C_INT_ERROR_MISSING_DEPENDENCY = -5;

		private static $m_arr_Schema = array();
		private static $m_arr_Paths = array();

		private static $m_str_Schema_Loc = "";
		private static $m_str_Json_Loc = "";
		private static $m_str_ErrorTxt = "";

		public static function add_Path($str_Path)
		{
			self::$m_arr_Paths[] = $str_Path;
		}

		public static function get_JsonFromFile($str_File)
		{
			$str_Json = file_get_contents($str_File);
			return json_decode($str_Json);
		}

		public static function get_LastSchemaPos()
		{
			return self::$m_str_Schema_Loc;
		}

		public static function get_ErrorText()
		{
			return self::$m_str_ErrorTxt;
		}

		public static function get_LastJsonPos()
		{
			return self::$m_str_Json_Loc;
		}

		/**
		 *
		 * Enter description here ...
		 * @param unknown_type $mix_JSON
		 * @param unknown_type $mix_Schema
		 * @return integer negative if error 0 or > if good
		 * - <b>-2</b> - Invalid Schema String passed
		 * - <b>-1</b> - Invalid Json String passed
		 * - <b>0</b> - Valid
		 */
		static public function validate($mix_JSON, $mix_Schema = false)
		{
			if (!is_object($mix_JSON))
			{
				$mix_JSON = json_decode($mix_JSON);

				if (!$mix_JSON)
				{
					return self::C_INT_ERROR_INVALID_JSON;
				}
			}

			if ($mix_Schema)
			{
				if (!is_object($mix_Schema))
				{
					$mix_Schema = json_decode($mix_Schema);

					if (!$mix_Schema)
					{
						return C_INT_ERROR_INVALID_SCHEMA;
					}
				}
			}
			else
			{
				return 0;
			}
			/*
			 //print_r($mix_Schema);
			 //print("\n\n\n\n\n\n\n");
			 foreach($mix_Schema as $key=>$item)
			 {
			 //print($key ." -> ");
			 //print_r($item);
																																				
			 //print("\n");
			 }
			 */
			self::$m_str_Schema_Loc = "/";
			self::$m_str_Json_Loc = "/";
			return self::validate_Section($mix_Schema, $mix_JSON);
		}

		static private function load_Ref($str_Ref)
		{
			if (isset(self::$m_arr_Schema[$str_Ref]))
			{
				return self::$m_arr_Schema[$str_Ref];
			}

			$str_SchemaFile = "";

			foreach (self::$m_arr_Paths as $str_Path)
			{
				if (file_exists($str_Path . "/" . $str_Ref))
				{
					$str_SchemaFile = $str_Path . "/" . $str_Ref;
					break;
				}
			}

			if ($str_SchemaFile)
			{
				$obj_Schema = self::get_JsonFromFile($str_SchemaFile);

				if ($obj_Schema)
				{
					self::$m_arr_Schema[$str_Ref] = $obj_Schema;
					return self::$m_arr_Schema[$str_Ref];
				}
			}

			return self::C_INT_ERROR_MISSING_DEPENDENCY;
		}
		/**
		 *
		 * Enter description here ...
		 * @param unknown_type $mix_JSON
		 * @param unknown_type $mix_Schema
		 * @return integer negative if error 0 or > if good
		 * - <b>-4</b> - Json Missing Required Data
		 * - <b>-3</b> - Json failed Schema Match
		 * - <b>0</b> - Valid
		 */
		static private function validate_Section($mix_SchemaSection, $mix_JsonSection)
		{
			//			//print_r($mix_SchemaSection);
			//			//print("\n\n\n\n\n\n\n");
			//			//print_r($mix_JsonSection);

			return self::type_Switch($mix_SchemaSection->type, $mix_SchemaSection, $mix_JsonSection);
		}

		static private function type_Switch($str_Type, &$mix_Schema, &$mix_Json)
		{
			$str_Json_Loc = self::$m_str_Json_Loc;
			$str_Schema_Loc = self::$m_str_Schema_Loc;

			switch ($str_Type)
			{
				case "object":
					if (!is_object($mix_Json))
					{
						////print("Fail Objectd");
						return C_INT_ERROR_MATCH_SCHEMA;
					}
					self::$m_str_Schema_Loc = $str_Schema_Loc . "properties/";
					$int_Return = self::validate_Properties($mix_Schema->properties, $mix_Json);
					////print("Object $int_Return\n");
					return $int_Return;
					break;
				case "array":
					if (!is_array($mix_Json))
					{
						////print("Fail Array");
						return C_INT_ERROR_MATCH_SCHEMA;
					}
					self::$m_str_Schema_Loc = $str_Schema_Loc . "items/";
					$int_Return = self::validate_Items($mix_Schema->items, $mix_Json);
					//print("Array $int_Return\n");
					return $int_Return;
					break;
				case "string":
					if (!is_string($mix_Json))
					{
						////print("Fail String");
						return C_INT_ERROR_MATCH_SCHEMA;
					}
					break;
				case "integer":
					if (!is_int($mix_Json))
					{
						////print("Fail Int");
						return C_INT_ERROR_MATCH_SCHEMA;
					}
					break;
			}
			return 0;
		}

		static private function validate_Properties($arr_Properties, $obj_Json)
		{
			$str_Json_Loc = self::$m_str_Json_Loc;
			$str_Schema_Loc = self::$m_str_Schema_Loc;

			//print("Schema Location - $str_Schema_Loc\n");
			//print("Json Location - $str_Json_Loc\n\n");
			/*			
			 if (strpos($str_Json_Loc, "/enhancement/CCE/CCEEntities/") !== false)
			 {
			 print("Schema ");
			 print_r($arr_Properties);
			 print("\n\n\n\n\n\n\n");
			 print("Json ");
			 print_r($obj_Json);
			 }
			 */
			foreach ($arr_Properties as $key => $data)
			{
				if (isset($obj_Json->$key))
				{
					self::$m_str_Json_Loc = $str_Json_Loc . $key . "/";
					self::$m_str_Schema_Loc = $str_Schema_Loc . $key . "/";
					$int_Return = self::type_Switch($data->type, $data, $obj_Json->$key);
					if ($int_Return < 0)
					{
						//print("Fail on Switch");
						return $int_Return;
					}
				}
				else
				{
					if (isset($data->required))
					{
						if ($data->required)
						{
							self::$m_str_ErrorTxt = "$key is required.";
							return self::C_INT_ERROR_MISSING_REQUIRED;
						}
					}
				}
			}
			return 0;
		}

		static private function validate_Items($obj_Items, $obj_Json)
		{
			$str_Json_Loc = self::$m_str_Json_Loc;
			$str_Schema_Loc = self::$m_str_Schema_Loc;

			//print("Schema Location - $str_Schema_Loc\n");
			//print("Json Location - $str_Json_Loc\n\n");

			$link = '$ref';
			if (isset($obj_Items->$link))
			{
				if ($obj_Items->$link)
				{
					$mix_Return = self::load_Ref($obj_Items->$link);

					if (!is_object($mix_Return))
					{
						try
						{
							if ($mix_Return < 0)
							{
								return $mix_Return;
							}
						}
						catch (Exception $e)
						{
							print $e . "\n";
						}
					}

					$obj_Items = $mix_Return;
				}
			}
			/*
			 if (strpos($str_Json_Loc, "/enhancement/CCE/CCEEntities/") !== false)
			 {
			 print("Schema ");
			 print_r($obj_Items);
			 print("\n\n\n\n\n\n\n");
			 print("Json ");
			 print_r($obj_Json);
			 }
			 */
			if (!isset($obj_Items->properties))
			{
				return C_INT_ERROR_INVALID_SCHEMA;
			}

			if (isset($obj_Items->minItems))
			{
				if (sizeof($obj_Json) < $obj_Items->minItems)
				{
					self::$m_str_ErrorTxt = "Your Array does not have the minimum amount of items.";
					////print("Size Problem\n");
					return self::C_INT_ERROR_MATCH_SCHEMA;
				}
			}

			foreach ($obj_Json as $key => $data)
			{
				self::$m_str_Schema_Loc = $str_Schema_Loc . "properties/";
				self::$m_str_Json_Loc = $str_Json_Loc . "[$key]/";

				$int_Return = self::validate_Properties($obj_Items->properties, $data);
				if ($int_Return < 0)
				{
					////print("Failed in Items Array $int_Reaturn\n");
					return $int_Return;
				}
			}

			return 0;
		}

		static public function convert_ErrorCode($int_ErrorCode)
		{
			switch ($int_ErrorCode)
			{
				case self::C_INT_ERROR_INVALID_JSON:
					return "The Json is invalid";
				case self::C_INT_ERROR_INVALID_SCHEMA:
					return "The Schema is invalid";
				case self::C_INT_ERROR_MATCH_SCHEMA:
					return "The Json does not match the schema";
				case self::C_INT_ERROR_MISSING_REQUIRED:
					return "The Json is missing required data.";
				case self::C_INT_ERROR_MISSING_DEPENDENCY:
					return "The Schema is missing a dependancy.";
			}
		}
	}
